<?php

namespace CoffeeScript;

class yy_Call extends yy_Base
{

    public $children = array('variable', 'args');
    private $is_new;
    private $is_super;

    function constructor($variable = NULL, $args = array(), $soak = FALSE)
    {
        $this->args = $args;
        $this->is_new = FALSE;
        $this->is_super = $variable === 'super';
        $this->variable = $this->is_super() ? NULL : $variable;
        $this->soak = $soak;

        return $this;
    }

    function compile_node($options)
    {
        if ($this->variable) {
            $this->variable->front = $this->front;
        }

        if (($code = yy_Splat::compile_splatted_array($options, $this->args, TRUE))) {
            return $this->compile_splat($options, $code);
        }

        $args = $this->filter_implicit_objects($this->args);
        $tmp = array();

        foreach ($args as $arg) {
            $tmp[] = $arg->compile($options, LEVEL_LIST);
        }

        $args = implode(', ', $tmp);

        if ($this->is_super()) {
            return $this->super_reference($options) . '.call(this' . ($args ? ', ' . $args : '') . ')';
        } else {
            return ($this->is_new() ? 'new ' : '') . $this->variable->compile($options, LEVEL_ACCESS) . "({$args})";
        }
    }

    function compile_super($args, $options)
    {
        return $this->super_reference($options) . '.call(this' . (count($args) ? ', ' : '') . $args . ')';
    }

    function compile_splat($options, $splat_args)
    {
        if ($this->is_super()) {
            return $this->super_reference($options) . '.apply(this, ' . $splat_args . ')';
        }

        if ($this->is_new()) {
            $idt = $this->tab . TAB;

            return
                    "(function(func, args, ctor) {\n"
                    . "{$idt}ctor.prototype = func.prototype;\n"
                    . "{$idt}var child = new ctor, result = func.apply(child, args), t = typeof result;\n"
                    . "{$idt}return t == \"object\" || t == \"function\" ? result || child : child;\n"
                    . "{$this->tab}})(" . $this->variable->compile($options, LEVEL_LIST) . ", $splat_args, function(){})";
        }

        $base = yy('Value', $this->variable);

        if (($name = array_pop($base->properties)) && $base->is_complex()) {
            $ref = $options['scope']->free_variable('ref');
            $fun = "($ref = " . $base->compile($options, LEVEL_LIST) . ')' . $name->compile($options) . '';
        } else {
            $fun = $base->compile($options, LEVEL_ACCESS);
            $fun = preg_match(SIMPLENUM, $fun) ? "($fun)" : $fun;

            if ($name) {
                $ref = $fun;
                $fun .= $name->compile($options);
            } else {
                $ref = NULL;
            }
        }

        $ref = $ref === NULL ? 'null' : $ref;

        return "{$fun}.apply({$ref}, {$splat_args})";
    }

    function is_new($set = NULL)
    {
        if ($set !== NULL) {
            $this->is_new = !!$set;
        }

        return $this->is_new;
    }

    function is_super()
    {
        return $this->is_super;
    }

    function filter_implicit_objects($list)
    {
        $nodes = array();

        foreach ($list as $node) {
            if (!($node->is_object() && $node->base->generated)) {
                $nodes[] = $node;
                continue;
            }

            $obj = NULL;

            foreach ($node->base->properties as $prop) {
                if (($prop instanceof yy_Assign) || $prop instanceof yy_Comment) {
                    if (!$obj) {
                        $nodes[] = ($obj = yy('Obj', array(), TRUE));
                    }

                    $obj->properties[] = $prop;
                } else {
                    $nodes[] = $prop;
                    $obj = NULL;
                }
            }
        }

        return $nodes;
    }

    function new_instance()
    {
        $base = isset($this->variable->base) ? $this->variable->base : $this->variable;

        if (($base instanceof yy_Call) && !$base->is_new()) {
            $base->new_instance();
        } else {
            $this->is_new = TRUE;
        }

        return $this;
    }

    function super_reference($options)
    {
        $method = $options['scope']->method;

        if ($method === NULL) {
            throw new SyntaxError('cannot call super outside of a function.');
        }

        $name = isset($method->name) ? $method->name : NULL;

        if ($name === NULL) {
            throw new SyntaxError('cannot call super on an anonymous function.');
        }

        if (isset($method->klass) && $method->klass) {
            $accesses = array(yy('Access', yy('Literal', '__super__')));

            if (isset($method->static) && $method->static) {
                $accesses[] = yy('Access', yy('Literal', 'constructor'));
            }

            $accesses[] = yy('Access', yy('Literal', $name));

            return yy('Value', yy('Literal', $method->klass), $accesses)->compile($options);
        } else {
            return $name . '.__super__.constructor';
        }
    }

    function unfold_soak($options = NULL)
    {
        if ($this->soak) {
            if ($this->variable) {
                if ($ifn = unfold_soak($options, $this, 'variable')) {
                    return $ifn;
                }

                $tmp = yy('Value', $this->variable);
                list($left, $rite) = $tmp->cache_reference($options);
            } else {
                $left = yy('Literal', $this->super_reference($options));
                $rite = yy('Value', $left);
            }

            $rite = yy('Call', $rite, $this->args);
            $rite->is_new($this->is_new());
            $left = yy('Literal', 'typeof ' . $left->compile($options) . ' === "function"');

            return yy('If', $left, yy('Value', $rite), array('soak' => TRUE));
        }

        $call = $this;
        $list = array();

        while (TRUE) {
            if ($call->variable instanceof yy_Call) {
                $list[] = $call;
                $call = $call->variable;

                continue;
            }

            if (!($call->variable instanceof yy_Value)) {
                break;
            }

            $list[] = $call;

            if (!(($call = $call->variable->base) instanceof yy_Call)) {
                break;
            }
        }

        foreach (array_reverse($list) as $call) {
            if (isset($ifn)) {
                if ($call->variable instanceof yy_Call) {
                    $call->variable = $ifn;
                } else {
                    $call->variable->base = $ifn;
                }
            }

            $ifn = unfold_soak($options, $call, 'variable');
        }

        return isset($ifn) ? $ifn : NULL;
    }

}

?>
